#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include "logger.h"

#define MAX_FILE_SIZE INT_MAX
#define MAX_VALUE_LENGTH 10
#define ARRAY_BLOCK_SIZE 256
#define handle_error(msg) { perror(msg); exit(EXIT_FAILURE); }

struct timeval tv1,tv2,dtv;
struct timezone tz;

void time_start() { gettimeofday(&tv1, &tz); }
long time_stop()
{ gettimeofday(&tv2, &tz);
  dtv.tv_sec= tv2.tv_sec -tv1.tv_sec;
  dtv.tv_usec=tv2.tv_usec-tv1.tv_usec;
  if(dtv.tv_usec<0) { dtv.tv_sec--; dtv.tv_usec+=1000000; }
  return dtv.tv_sec*1000+dtv.tv_usec/1000;
}

inline int is_digit(char c) {
	return ( c <= '9' && c >= '0'); 
}
void word_push_back(char ** word, int size, char c) {
	if (size == 0) {
		*word = (char*)malloc(sizeof(char) * (MAX_VALUE_LENGTH + 1));
	}
	(*word)[size] = c;
	(*word)[size + 1] = '\0';
}

void array_push_back(int ** array, int size, int value) {
	if (size == 0) {
		*array = (int*)malloc(sizeof(int) * ARRAY_BLOCK_SIZE);
	} else {
		if ((size % ARRAY_BLOCK_SIZE) == 0)
		*array = (int*)realloc(*array, (sizeof(int)) * (size + 1) * ARRAY_BLOCK_SIZE);
	}
	(*array)[size] = value;
}

int* parse_csv_int(char *array, int size, int *arr_size) {
	int position = 0,
		current_size = 0,
		word_size = 0,
		finalized = 0;

	char *number;
	int* output_array = NULL;

	while (position < size) {
		
		char ch = array[position];
		if (is_digit(ch) && (word_size < MAX_VALUE_LENGTH)) {
			word_push_back(&number, word_size++, ch);
		} else {
			if (word_size > MAX_VALUE_LENGTH) {
					LOG_PRINT(ERROR, "Data is out if range(%s length:%d), abort", number, word_size);
					LOG_FINALIZE();
					handle_error("max input value size exceeded");
			}
			switch (ch) {
				case '-' :  if (!word_size) {
								word_push_back(&number, word_size++, ch);		
							} else {
								LOG_PRINT(WARNING, "parseCSVToInt failed, maybe corrupted data(%s)", number);	
							}		
							break;
				case ',' :	if (!word_size) {
								LOG_PRINT(WARNING, "parseCSVToInt failed, maybe corrupted data(%s)", number);	
							} else {
								finalized = 1;
							}
							break;
				default  :  LOG_PRINT(WARNING, "parseCSVToInt failed, maybe corrupted data(%s)", number);
							break;
			}
		}
		if (finalized || (position == size - 1)) {
			char *end_ptr;
			int num_value = (int)strtol(number, &end_ptr, 10);
				if (*end_ptr != '\0' || errno == ERANGE) {
					LOG_PRINT(WARNING, "parseCSVToInt failed, maybe corrupted data(%s)", number);
				}
			array_push_back(&output_array, current_size++, num_value);
			finalized = 0;
			word_size = 0;
			free(number);
		}
		++position;
	}

	*arr_size = current_size;
	return output_array;
}

void sub_merge(int *A, int p, int q, int r) {
   int i,j,k;
   int *c;
     i = p;j = r+1; k = 0;
     c = (int*)malloc(sizeof(int)*(q-p+1));

     while (k<(q-p+1)) {

      if (A[i]<A[j]) {c[k]=A[i];i++;k++;}
      else {c[k]=A[j];j++;k++;};

      if (i>r && j<=q) {
      		for (;j<=q;j++) {c[k]=A[j];k++;};  /*Main algorithm  - merger sort*/
      		break;
      }
      if (i<=r && j>q) {
      		for (;i<=r;i++) {c[k]=A[i];k++;};
      		break;
      }
     }
   for (i=0;i<(q-p+1);i++) A[p+i]=c[i];
   free(c);
}

void merge_sort( int *A,int p,int q) {
	int r;
 	if(p == q)return;
    r = (q+p)/2;
 	merge_sort(A,p,r);
 	merge_sort(A,r+1,q);

 	sub_merge(A,p,q,r);
}


int main(int argc, char *argv[]){

	int fd_input, fd_output; //file descriptor
	struct stat fileStat;
	size_t size;
	char *input, *output;

	LOG_INIT("log.txt", 1024);
	SET_LOG_LEVEL(INFO);
	LOG_PRINT(INFO, "file mapping executed");

	if (argc < 3) {
		handle_error("lack of arguments");
	}

		time_start();

		fd_input = open(argv[1], O_RDONLY);
		fd_output = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0666);
			if (!((fd_input != -1) && (fd_output != -1))) {
				LOG_PRINT(ERROR, "Opening Descritor failed");
					close(fd_input);
					close(fd_output);
				LOG_FINALIZE();
				handle_error("open descriptor failed");
			}
			if (fstat(fd_input, &fileStat) == -1) {
				LOG_PRINT(ERROR,"Can't get input file info");
					close(fd_input);
					close(fd_output);
				handle_error("fstat failed");
			}
			if (fileStat.st_size > MAX_FILE_SIZE) {
				LOG_PRINT(ERROR, "Input File exceeds(max.size:%d)", MAX_FILE_SIZE);
					close(fd_input);
					close(fd_output);
				LOG_FINALIZE();
				handle_error("max file size exceeded");
			}
		size = fileStat.st_size;
		//"resize" output file
        if ((((int)size - 1) - 1) < 0) {
            LOG_PRINT(ERROR, "Maybe some problems with input file. size : %d\n", size);
                close(fd_input);
                close(fd_output);
            LOG_FINALIZE();
            handle_error("bad input file");
        }
		    assert(lseek(fd_output, (size - 1) - 1, SEEK_SET) != -1);	//This part of code was taken from PetrovM
		    assert(write(fd_output, "", 1) == 1);

		input = (char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fd_input, 0);
		output =(char*)mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd_output, 0);
		LOG_PRINT(INFO, "Input, output mapped successfully");
			if (output == MAP_FAILED) {
					LOG_PRINT(ERROR, "mmap output failed");
						close(fd_input);
						close(fd_output);
					LOG_FINALIZE();
					handle_error("output mmap failed");
			}
			if (input == MAP_FAILED) {
					LOG_PRINT(ERROR, "mmap output failed");
						close(fd_input);
						close(fd_output);
					LOG_FINALIZE();
					handle_error("mmap input failed")
				}
		LOG_PRINT(INFO,"All resources allocated successfully");
		
		int arr_size;
		int* array = parse_csv_int(input, (int)size, &arr_size);
			if (array == NULL) {
                LOG_PRINT(ERROR, "Parsing CVS failure");
                LOG_FINALIZE();
				handle_error("parse failure");
			}
		merge_sort(array, 0, arr_size -1);		
		//writing block to output
		{
			int current_position = 0,
				i;

			for (i = 0; i < arr_size; ++i) {
				char buffer[20];
				sprintf(buffer, "%d,", array[i]);
				sprintf(&output[current_position], "%d,", array[i]);
				current_position += strlen(buffer);
			}
		}
			if (munmap(input, size) == -1) {
					LOG_PRINT(ERROR, "Failed to unmap input file");
					LOG_FINALIZE();
					handle_error("Error un-mmapping input file");
			}
			if (munmap(output, size) == -1) {
					LOG_PRINT(ERROR, "Failed to unmap output file");
					LOG_FINALIZE();
					handle_error("Error un-mmaping output file");
			}
	
		close(fd_input);
		close(fd_output);
		free(array);

		LOG_PRINT(INFO, "CVS file sorted, mapping worked successfully, deallocating finilized.");
		LOG_FINALIZE();

		//printf("Time %ld\n", time_stop());
		return 0;
}
